{
 "cells": [
  {
   "cell_type": "code",
   "id": "initial_id",
   "metadata": {
    "collapsed": true,
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:38.493872Z",
     "start_time": "2025-01-17T04:28:33.643989Z"
    }
   },
   "source": [
    "import matplotlib as mpl\n",
    "import matplotlib.pyplot as plt\n",
    "%matplotlib inline\n",
    "import numpy as np\n",
    "import sklearn\n",
    "import pandas as pd\n",
    "import os\n",
    "import sys\n",
    "import time\n",
    "from tqdm.auto import tqdm\n",
    "import torch\n",
    "import torch.nn as nn\n",
    "import torch.nn.functional as F\n",
    "\n",
    "print(sys.version_info)\n",
    "for module in mpl, np, pd, sklearn, torch:\n",
    "    print(module.__name__, module.__version__)\n",
    "\n",
    "device = torch.device(\"cuda:0\") if torch.cuda.is_available() else torch.device(\"cpu\")\n",
    "print(device)  #设备是cuda:0，即GPU，如果没有GPU则是cpu\n"
   ],
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "sys.version_info(major=3, minor=12, micro=3, releaselevel='final', serial=0)\n",
      "matplotlib 3.10.0\n",
      "numpy 1.26.4\n",
      "pandas 2.2.3\n",
      "sklearn 1.6.0\n",
      "torch 2.5.1+cpu\n",
      "cpu\n"
     ]
    }
   ],
   "execution_count": 1
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 数据准备",
   "id": "1073bc64c567ba13"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:40.304187Z",
     "start_time": "2025-01-17T04:28:38.494888Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torchvision import datasets\n",
    "from torchvision.transforms import ToTensor\n",
    "\n",
    "# 训练集\n",
    "train_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=True,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# 测试集\n",
    "test_ds = datasets.FashionMNIST(\n",
    "    root=\"data\",\n",
    "    train=False,\n",
    "    download=True,\n",
    "    transform=ToTensor()\n",
    ")\n",
    "\n",
    "# 从数据集到dataloader\n",
    "train_loader = torch.utils.data.DataLoader(train_ds, batch_size=16, shuffle=True)\n",
    "test_loader = torch.utils.data.DataLoader(test_ds, batch_size=16, shuffle=False)\n"
   ],
   "id": "55788f25672e1ebe",
   "outputs": [],
   "execution_count": 2
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.312305Z",
     "start_time": "2025-01-17T04:28:40.305189Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from torchvision.transforms import Normalize\n",
    "\n",
    "\n",
    "# 遍历train_ds得到每张图片，计算每个通道的均值和方差\n",
    "def cal_mean_std(ds):\n",
    "    mean = 0.\n",
    "    std = 0.\n",
    "    for img, _ in ds:\n",
    "        # img.shape=[1,28,28]\n",
    "        mean += img.mean(dim=(1, 2))\n",
    "        std += img.std(dim=(1, 2))\n",
    "    mean /= len(ds)\n",
    "    std /= len(ds)\n",
    "    return mean, std\n",
    "\n",
    "\n",
    "_mean, _std = cal_mean_std(train_ds)\n",
    "print(_mean, _std)\n",
    "\n",
    "transforms = nn.Sequential(\n",
    "    Normalize(mean=_mean, std=_std)\n",
    ")"
   ],
   "id": "7296e2499aa5be79",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "tensor([0.2860]) tensor([0.3205])\n"
     ]
    }
   ],
   "execution_count": 3
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 定义模型",
   "id": "b5e4f17ae389c008"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.331705Z",
     "start_time": "2025-01-17T04:28:47.313309Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class NeuralNetwork(nn.Module):\n",
    "    def __init__(self, layers_num=10):\n",
    "        super().__init__()\n",
    "        self.transform = transforms  # 预处理层，标准化\n",
    "        self.flatten = nn.Flatten()  # 展平层\n",
    "        self.linear_relu_stack = nn.Sequential(  # 线性层+激活层堆叠\n",
    "            nn.Linear(28 * 28, 100),\n",
    "            # bn：批归一化层，对输入数据进行归一化处理，使得数据分布更加一致，加快模型收敛速度\n",
    "            # 一般而言，先batch norm 后 激活函数\n",
    "            # 1d：表示输入的维度是1\n",
    "            nn.BatchNorm1d(100), # num of features=100\n",
    "            nn.ReLU(),\n",
    "        )\n",
    "        # 加layers_num-1个线性层+激活层\n",
    "        for i in range(1, layers_num):\n",
    "            self.linear_relu_stack.add_module(f\"linear{i}\", nn.Linear(100, 100))\n",
    "            self.linear_relu_stack.add_module(f\"bn{i}\", nn.BatchNorm1d(100))\n",
    "            self.linear_relu_stack.add_module(f\"relu{i}\", nn.ReLU())\n",
    "        # 输出层\n",
    "        self.linear_relu_stack.add_module(\"linear_out\", nn.Linear(100, 10))\n",
    "        # 初始化权重\n",
    "        self.init_weights()\n",
    "\n",
    "    def init_weights(self):\n",
    "        print(\"初始化权重\")\n",
    "        for m in self.modules():\n",
    "            if isinstance(m, nn.Linear):\n",
    "                nn.init.xavier_uniform_(m.weight)  # xavier 均匀分布初始化权重\n",
    "                nn.init.zeros_(m.bias)  # 全零初始化偏置项\n",
    "        print(\"权重初始化完成\")\n",
    "\n",
    "    def forward(self, x):  # 前向传播\n",
    "        x = self.transform(x)  # 预处理,标准化\n",
    "        # 将输入数据 x 展平（Flatten），通常用于将多维数据转换为一维数据。\n",
    "        x = self.flatten(x)\n",
    "        # logits 是模型的原始输出，通常是未经过 Softmax 或 Sigmoid 处理的分数。\n",
    "        logits = self.linear_relu_stack(x)\n",
    "        return logits\n",
    "\n",
    "\n",
    "# 计算模型参数数量\n",
    "total = 0\n",
    "for idx, (key, value) in enumerate(NeuralNetwork(20).named_parameters()):\n",
    "    print(f\"Linear_{idx // 2:>02}\\tparamerters num: {np.prod(value.shape)}\")  #np.prod是计算张量的元素个数\n",
    "    total += np.prod(value.shape) \n",
    "total"
   ],
   "id": "d3016623476b4fd4",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "初始化权重\n",
      "权重初始化完成\n",
      "Linear_00\tparamerters num: 78400\n",
      "Linear_00\tparamerters num: 100\n",
      "Linear_01\tparamerters num: 100\n",
      "Linear_01\tparamerters num: 100\n",
      "Linear_02\tparamerters num: 10000\n",
      "Linear_02\tparamerters num: 100\n",
      "Linear_03\tparamerters num: 100\n",
      "Linear_03\tparamerters num: 100\n",
      "Linear_04\tparamerters num: 10000\n",
      "Linear_04\tparamerters num: 100\n",
      "Linear_05\tparamerters num: 100\n",
      "Linear_05\tparamerters num: 100\n",
      "Linear_06\tparamerters num: 10000\n",
      "Linear_06\tparamerters num: 100\n",
      "Linear_07\tparamerters num: 100\n",
      "Linear_07\tparamerters num: 100\n",
      "Linear_08\tparamerters num: 10000\n",
      "Linear_08\tparamerters num: 100\n",
      "Linear_09\tparamerters num: 100\n",
      "Linear_09\tparamerters num: 100\n",
      "Linear_10\tparamerters num: 10000\n",
      "Linear_10\tparamerters num: 100\n",
      "Linear_11\tparamerters num: 100\n",
      "Linear_11\tparamerters num: 100\n",
      "Linear_12\tparamerters num: 10000\n",
      "Linear_12\tparamerters num: 100\n",
      "Linear_13\tparamerters num: 100\n",
      "Linear_13\tparamerters num: 100\n",
      "Linear_14\tparamerters num: 10000\n",
      "Linear_14\tparamerters num: 100\n",
      "Linear_15\tparamerters num: 100\n",
      "Linear_15\tparamerters num: 100\n",
      "Linear_16\tparamerters num: 10000\n",
      "Linear_16\tparamerters num: 100\n",
      "Linear_17\tparamerters num: 100\n",
      "Linear_17\tparamerters num: 100\n",
      "Linear_18\tparamerters num: 10000\n",
      "Linear_18\tparamerters num: 100\n",
      "Linear_19\tparamerters num: 100\n",
      "Linear_19\tparamerters num: 100\n",
      "Linear_20\tparamerters num: 10000\n",
      "Linear_20\tparamerters num: 100\n",
      "Linear_21\tparamerters num: 100\n",
      "Linear_21\tparamerters num: 100\n",
      "Linear_22\tparamerters num: 10000\n",
      "Linear_22\tparamerters num: 100\n",
      "Linear_23\tparamerters num: 100\n",
      "Linear_23\tparamerters num: 100\n",
      "Linear_24\tparamerters num: 10000\n",
      "Linear_24\tparamerters num: 100\n",
      "Linear_25\tparamerters num: 100\n",
      "Linear_25\tparamerters num: 100\n",
      "Linear_26\tparamerters num: 10000\n",
      "Linear_26\tparamerters num: 100\n",
      "Linear_27\tparamerters num: 100\n",
      "Linear_27\tparamerters num: 100\n",
      "Linear_28\tparamerters num: 10000\n",
      "Linear_28\tparamerters num: 100\n",
      "Linear_29\tparamerters num: 100\n",
      "Linear_29\tparamerters num: 100\n",
      "Linear_30\tparamerters num: 10000\n",
      "Linear_30\tparamerters num: 100\n",
      "Linear_31\tparamerters num: 100\n",
      "Linear_31\tparamerters num: 100\n",
      "Linear_32\tparamerters num: 10000\n",
      "Linear_32\tparamerters num: 100\n",
      "Linear_33\tparamerters num: 100\n",
      "Linear_33\tparamerters num: 100\n",
      "Linear_34\tparamerters num: 10000\n",
      "Linear_34\tparamerters num: 100\n",
      "Linear_35\tparamerters num: 100\n",
      "Linear_35\tparamerters num: 100\n",
      "Linear_36\tparamerters num: 10000\n",
      "Linear_36\tparamerters num: 100\n",
      "Linear_37\tparamerters num: 100\n",
      "Linear_37\tparamerters num: 100\n",
      "Linear_38\tparamerters num: 10000\n",
      "Linear_38\tparamerters num: 100\n",
      "Linear_39\tparamerters num: 100\n",
      "Linear_39\tparamerters num: 100\n",
      "Linear_40\tparamerters num: 1000\n",
      "Linear_40\tparamerters num: 10\n"
     ]
    },
    {
     "data": {
      "text/plain": [
       "275410"
      ]
     },
     "execution_count": 4,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "execution_count": 4
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 训练模型",
   "id": "8fb5dfcd77513c03"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.390354Z",
     "start_time": "2025-01-17T04:28:47.333725Z"
    }
   },
   "cell_type": "code",
   "source": [
    "from sklearn.metrics import accuracy_score\n",
    "\n",
    "\n",
    "@torch.no_grad()  # 装饰器，禁止梯度计算\n",
    "def evaluate(model, data_loader, loss_fct):\n",
    "    loss_list = []\n",
    "    pred_list = []\n",
    "    label_list = []\n",
    "    for datas, labels in data_loader:\n",
    "        datas = datas.to(device)\n",
    "        labels = labels.to(device)\n",
    "\n",
    "        # 前向传播\n",
    "        logits = model(datas)\n",
    "        loss = loss_fct(logits, labels)  # 验证集损失\n",
    "        # tensor.item() 获取tensor的数值，loss是只有一个元素的tensor\n",
    "        loss_list.append(loss.item())\n",
    "\n",
    "        # 预测\n",
    "        preds = logits.argmax(axis=-1)  # 预测类别\n",
    "        pred_list.extend(preds.cpu().numpy().tolist())  # tensor转numpy，再转list\n",
    "        label_list.extend(labels.cpu().numpy().tolist())\n",
    "\n",
    "    acc = accuracy_score(label_list, pred_list)  # 计算准确率\n",
    "    return np.mean(loss_list), acc  # # 返回验证集平均损失和准确率\n"
   ],
   "id": "24550945260b9755",
   "outputs": [],
   "execution_count": 5
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## Save Best Model",
   "id": "6fd06a23a5bccc23"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.396436Z",
     "start_time": "2025-01-17T04:28:47.391371Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class SaveCheckpointsCallback:\n",
    "    def __init__(self, save_dir, save_step=500, save_best_only=True):\n",
    "        self.save_dir = save_dir  # 保存路径\n",
    "        self.save_step = save_step  # 保存步数\n",
    "        self.save_best_only = save_best_only  # 是否只保存最好的模型\n",
    "        self.best_metric = -1  # 最好的指标，指标不可能为负数，所以初始化为-1\n",
    "        # 创建保存路径\n",
    "        if not os.path.exists(self.save_dir):  # 如果不存在保存路径，则创建\n",
    "            os.makedirs(self.save_dir)\n",
    "\n",
    "    # 对象被调用时：当你将对象像函数一样调用时，Python 会自动调用 __call__ 方法。\n",
    "    # state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "    # metric 是指标，可以是验证集的准确率，也可以是其他指标\n",
    "    def __call__(self, step, state_dict, metric=None):\n",
    "        if step % self.save_step > 0:\n",
    "            return  # 不是保存步数，则直接返回\n",
    "\n",
    "        if self.save_best_only:\n",
    "            assert metric is not None  # 必须传入metric\n",
    "            if metric >= self.best_metric:  # 如果当前指标大于最好的指标\n",
    "                # save checkpoint\n",
    "                # 保存最好的模型，覆盖之前的模型，不保存step，只保存state_dict，即模型参数，不保存优化器参数\n",
    "                torch.save(state_dict, os.path.join(self.save_dir, \"05_best.ckpt\"))\n",
    "                self.best_metric = metric  # 更新最好的指标\n",
    "        else:\n",
    "            # 保存模型\n",
    "            torch.save(state_dict, os.path.join(self.save_dir, f\"{step}.ckpt\"))\n",
    "            # 保存每个step的模型，不覆盖之前的模型，保存step，保存state_dict，即模型参数，不保存优化器参数\n"
   ],
   "id": "e93aec97aec45c4e",
   "outputs": [],
   "execution_count": 6
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## Early Stopping",
   "id": "3f9a004729448217"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.402176Z",
     "start_time": "2025-01-17T04:28:47.397438Z"
    }
   },
   "cell_type": "code",
   "source": [
    "class EarlyStopCallback:\n",
    "    def __init__(self, patience=5, min_delta=0.01):\n",
    "        self.patience = patience  # 多少个step没有提升就停止训练\n",
    "        self.min_delta = min_delta  # 最小的提升幅度\n",
    "        self.best_metric = -1  # 记录的最好的指标\n",
    "        self.counter = 0  # 计数器，记录连续多少个step没有提升\n",
    "\n",
    "    def __call__(self, metric):\n",
    "        if metric >= self.best_metric + self.min_delta:  # 如果指标提升了\n",
    "            self.best_metric = metric  # 更新最好的指标\n",
    "            self.counter = 0  # 计数器清零\n",
    "        else:\n",
    "            self.counter += 1  # 计数器加一\n",
    "\n",
    "    @property  # 使用@property装饰器，使得 对象.early_stop可以调用，不需要()\n",
    "    def early_stop(self):\n",
    "        # 如果计数器大于等于patience，则返回True，停止训练\n",
    "        return self.counter >= self.patience\n"
   ],
   "id": "d8fdc303a4d27c00",
   "outputs": [],
   "execution_count": 7
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 训练",
   "id": "b56dab87e8ff6485"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.410483Z",
     "start_time": "2025-01-17T04:28:47.403179Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def training(model,\n",
    "             train_loader,\n",
    "             test_loader,\n",
    "             epoch,\n",
    "             loss_fct,\n",
    "             optimizer,\n",
    "             tensorboard_callback=None,\n",
    "             save_ckpt_callback=None,\n",
    "             early_stop_callback=None,\n",
    "             eval_step=500,\n",
    "             ):\n",
    "    record_dict = {\n",
    "        \"train\": [],\n",
    "        \"val\": []\n",
    "    }\n",
    "\n",
    "    global_step = 0  # 全局步数\n",
    "    model.train()  # 训练模式\n",
    "    with tqdm(total=epoch * len(train_loader)) as pbar:\n",
    "        for epoch_id in range(epoch):\n",
    "            for datas, labels in train_loader:\n",
    "                datas = datas.to(device)\n",
    "                labels = labels.to(device)\n",
    "\n",
    "                # 前向传播\n",
    "                logits = model(datas)\n",
    "                loss = loss_fct(logits, labels)  # 训练集损失\n",
    "                preds = logits.argmax(axis=-1)  # 预测类别\n",
    "\n",
    "                # 反向传播\n",
    "                optimizer.zero_grad()  # 梯度清零\n",
    "                loss.backward()  # 反向传播\n",
    "                optimizer.step()  # 优化器更新参数\n",
    "\n",
    "                # 计算准确率\n",
    "                acc = accuracy_score(labels.cpu().numpy(), preds.cpu().numpy())\n",
    "                loss = loss.cpu().item()\n",
    "\n",
    "                record_dict[\"train\"].append({\n",
    "                    \"loss\": loss,\n",
    "                    \"acc\": acc,\n",
    "                    \"step\": global_step\n",
    "                })\n",
    "\n",
    "                # 评估\n",
    "                if global_step % eval_step == 0:\n",
    "                    model.eval()  # 评估模式\n",
    "                    # 验证集损失和准确率\n",
    "                    val_loss, val_acc = evaluate(model, test_loader, loss_fct)\n",
    "                    record_dict[\"val\"].append({\n",
    "                        \"loss\": val_loss,\n",
    "                        \"acc\": val_acc,\n",
    "                        \"step\": global_step\n",
    "                    })\n",
    "                    model.train()  # 训练模式\n",
    "\n",
    "                    # 1. 使用 tensorboard 可视化\n",
    "                    if tensorboard_callback is not None:\n",
    "                        tensorboard_callback(\n",
    "                            global_step,\n",
    "                            loss=loss, val_loss=val_loss,\n",
    "                            acc=acc, val_acc=val_acc,\n",
    "                            lr=optimizer.param_groups[0][\"lr\"],  # 取出当前学习率\n",
    "                        )\n",
    "\n",
    "                    # 2. 保存模型权重 save model checkpoint\n",
    "                    if save_ckpt_callback is not None:\n",
    "                        # model.state_dict() 返回模型参数的字典，包括模型参数和优化器参数\n",
    "                        save_ckpt_callback(global_step, model.state_dict(), val_acc)\n",
    "                        # 保存最好的模型，覆盖之前的模型，保存step，保存state_dict,通过metric判断是否保存最好的模型\n",
    "\n",
    "                    # 3. 早停 early stopping\n",
    "                    if early_stop_callback is not None:\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        early_stop_callback(val_acc)\n",
    "                        # 验证集准确率不再提升，则停止训练\n",
    "                        if early_stop_callback.early_stop:\n",
    "                            print(f\"Early stop at epoch {epoch_id} / global_step {global_step}\")\n",
    "                            return record_dict  # 早停，返回记录字典 record_dict\n",
    "\n",
    "                # 更新进度条和全局步数\n",
    "                pbar.update(1)  # 更新进度条\n",
    "                global_step += 1  # 全局步数加一\n",
    "                pbar.set_postfix({\"epoch\": epoch_id})\n",
    "\n",
    "    return record_dict  # 训练结束，返回记录字典 record_dict\n"
   ],
   "id": "2602ccd3cb633f69",
   "outputs": [],
   "execution_count": 8
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "",
   "id": "d111e077fd6e015"
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:28:47.420530Z",
     "start_time": "2025-01-17T04:28:47.411487Z"
    }
   },
   "cell_type": "code",
   "source": [
    "epoch = 100\n",
    "\n",
    "model = NeuralNetwork()\n",
    "\n",
    "# 1. 定义损失函数 采用MSE损失\n",
    "loss_fct = nn.CrossEntropyLoss()\n",
    "\n",
    "# 2. 定义优化器 采用SGD优化器\n",
    "optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9)\n",
    "\n",
    "# 3.save model checkpoint\n",
    "save_ckpt_callback = SaveCheckpointsCallback(save_dir=\"checkpoints\", save_step=500, save_best_only=True)\n",
    "\n",
    "# 4. early stopping\n",
    "early_stop_callback = EarlyStopCallback(patience=10, min_delta=0.01)"
   ],
   "id": "8b42caff09582576",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "初始化权重\n",
      "权重初始化完成\n"
     ]
    }
   ],
   "execution_count": 9
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:37:52.352321Z",
     "start_time": "2025-01-17T04:28:47.421533Z"
    }
   },
   "cell_type": "code",
   "source": [
    "model = model.to(device)  # 将模型移到GPU上\n",
    "\n",
    "# 训练过程\n",
    "record_dict = training(\n",
    "    model,\n",
    "    train_loader,\n",
    "    test_loader,\n",
    "    epoch,\n",
    "    loss_fct,\n",
    "    optimizer,\n",
    "    tensorboard_callback=None,\n",
    "    save_ckpt_callback=save_ckpt_callback,\n",
    "    early_stop_callback=early_stop_callback,\n",
    "    eval_step=len(train_loader)\n",
    ")"
   ],
   "id": "1b4d7f1cfe612c71",
   "outputs": [
    {
     "data": {
      "text/plain": [
       "  0%|          | 0/375000 [00:00<?, ?it/s]"
      ],
      "application/vnd.jupyter.widget-view+json": {
       "version_major": 2,
       "version_minor": 0,
       "model_id": "939b086d44d144709cd378f688ec2269"
      }
     },
     "metadata": {},
     "output_type": "display_data"
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Early stop at epoch 20 / global_step 75000\n"
     ]
    }
   ],
   "execution_count": 10
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:37:53.242389Z",
     "start_time": "2025-01-17T04:37:52.352321Z"
    }
   },
   "cell_type": "code",
   "source": [
    "def plot_record_curves(record_dict, sample_step=1000):\n",
    "    # .set_index(\"step\") 将 step 列设置为 DataFrame 的索引\n",
    "    train_df = pd.DataFrame(record_dict[\"train\"]).set_index(\"step\").iloc[::sample_step]\n",
    "    val_df = pd.DataFrame(record_dict[\"val\"]).set_index(\"step\")\n",
    "\n",
    "    last_step = train_df.index[-1]  # 最后一步的步数\n",
    "\n",
    "    print(train_df)\n",
    "    print(val_df)\n",
    "\n",
    "    # 画图 \n",
    "    fig_num = len(train_df.columns)  # 画两张图,分别是损失和准确率\n",
    "\n",
    "    # plt.subplots：用于创建一个包含多个子图的图形窗口。\n",
    "    # 1：表示子图的行数为 1。\n",
    "    # fig_num：表示子图的列数，即子图的数量。\n",
    "    # figsize=(5 * fig_num, 5)：设置整个图形窗口的大小，宽度为 5 * fig_num，高度为 5。\n",
    "    # fig：返回的图形对象（Figure），用于操作整个图形窗口。\n",
    "    # axs：返回的子图对象（Axes 或 Axes 数组），用于操作每个子图。\n",
    "    fig, axs = plt.subplots(1, fig_num, figsize=(5 * fig_num, 5))\n",
    "    for idx, item in enumerate(train_df.columns):\n",
    "        # train_df.index 是 x 轴数据（通常是 step）。\n",
    "        # train_df[item] 是 y 轴数据（当前指标的值）。\n",
    "        axs[idx].plot(train_df.index, train_df[item], label=\"train:\" + item)\n",
    "        # val_df.index 是 x 轴数据。\n",
    "        # val_df[item] 是 y 轴数据。\n",
    "        axs[idx].plot(val_df.index, val_df[item], label=\"val:\" + item)\n",
    "        axs[idx].grid()  # 显示网格\n",
    "        axs[idx].legend()  # 显示图例\n",
    "        axs[idx].set_xticks(range(0, train_df.index[-1], 5000))  # 设置x轴刻度\n",
    "        axs[idx].set_xticklabels(map(lambda x: f\"{x // 1000}k\", range(0, last_step + 1, 5000)))  # 设置x轴标签\n",
    "        axs[idx].set_xlabel(\"step\")\n",
    "\n",
    "    plt.show()\n",
    "\n",
    "\n",
    "plot_record_curves(record_dict)"
   ],
   "id": "de28437c6058f608",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "           loss     acc\n",
      "step                   \n",
      "0      2.446184  0.1250\n",
      "1000   0.878825  0.5625\n",
      "2000   1.172826  0.5000\n",
      "3000   0.584995  0.6875\n",
      "4000   0.282803  0.9375\n",
      "...         ...     ...\n",
      "71000  0.256144  0.8750\n",
      "72000  0.316860  0.8125\n",
      "73000  0.250772  0.8750\n",
      "74000  0.653931  0.7500\n",
      "75000  0.710522  0.8125\n",
      "\n",
      "[76 rows x 2 columns]\n",
      "           loss     acc\n",
      "step                   \n",
      "0      2.305601  0.1220\n",
      "3750   0.512907  0.8266\n",
      "7500   0.466040  0.8413\n",
      "11250  0.426274  0.8517\n",
      "15000  0.416329  0.8579\n",
      "18750  0.400060  0.8660\n",
      "22500  0.380711  0.8716\n",
      "26250  0.388717  0.8698\n",
      "30000  0.397137  0.8685\n",
      "33750  0.378827  0.8676\n",
      "37500  0.370861  0.8776\n",
      "41250  0.363304  0.8771\n",
      "45000  0.375598  0.8694\n",
      "48750  0.365486  0.8764\n",
      "52500  0.352869  0.8759\n",
      "56250  0.360455  0.8785\n",
      "60000  0.346482  0.8784\n",
      "63750  0.353406  0.8793\n",
      "67500  0.345211  0.8826\n",
      "71250  0.346580  0.8816\n",
      "75000  0.338970  0.8801\n"
     ]
    },
    {
     "ename": "ValueError",
     "evalue": "The number of FixedLocator locations (15), usually from a call to set_ticks, does not match the number of labels (16).",
     "output_type": "error",
     "traceback": [
      "\u001B[1;31m---------------------------------------------------------------------------\u001B[0m",
      "\u001B[1;31mValueError\u001B[0m                                Traceback (most recent call last)",
      "Cell \u001B[1;32mIn[11], line 37\u001B[0m\n\u001B[0;32m     32\u001B[0m         axs[idx]\u001B[38;5;241m.\u001B[39mset_xlabel(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mstep\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m     34\u001B[0m     plt\u001B[38;5;241m.\u001B[39mshow()\n\u001B[1;32m---> 37\u001B[0m \u001B[43mplot_record_curves\u001B[49m\u001B[43m(\u001B[49m\u001B[43mrecord_dict\u001B[49m\u001B[43m)\u001B[49m\n",
      "Cell \u001B[1;32mIn[11], line 31\u001B[0m, in \u001B[0;36mplot_record_curves\u001B[1;34m(record_dict, sample_step)\u001B[0m\n\u001B[0;32m     29\u001B[0m     axs[idx]\u001B[38;5;241m.\u001B[39mlegend()  \u001B[38;5;66;03m# 显示图例\u001B[39;00m\n\u001B[0;32m     30\u001B[0m     axs[idx]\u001B[38;5;241m.\u001B[39mset_xticks(\u001B[38;5;28mrange\u001B[39m(\u001B[38;5;241m0\u001B[39m, train_df\u001B[38;5;241m.\u001B[39mindex[\u001B[38;5;241m-\u001B[39m\u001B[38;5;241m1\u001B[39m], \u001B[38;5;241m5000\u001B[39m))  \u001B[38;5;66;03m# 设置x轴刻度\u001B[39;00m\n\u001B[1;32m---> 31\u001B[0m     \u001B[43maxs\u001B[49m\u001B[43m[\u001B[49m\u001B[43midx\u001B[49m\u001B[43m]\u001B[49m\u001B[38;5;241;43m.\u001B[39;49m\u001B[43mset_xticklabels\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mmap\u001B[39;49m\u001B[43m(\u001B[49m\u001B[38;5;28;43;01mlambda\u001B[39;49;00m\u001B[43m \u001B[49m\u001B[43mx\u001B[49m\u001B[43m:\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;124;43mf\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[38;5;132;43;01m{\u001B[39;49;00m\u001B[43mx\u001B[49m\u001B[38;5;250;43m \u001B[39;49m\u001B[38;5;241;43m/\u001B[39;49m\u001B[38;5;241;43m/\u001B[39;49m\u001B[38;5;250;43m \u001B[39;49m\u001B[38;5;241;43m1000\u001B[39;49m\u001B[38;5;132;43;01m}\u001B[39;49;00m\u001B[38;5;124;43mk\u001B[39;49m\u001B[38;5;124;43m\"\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;28;43mrange\u001B[39;49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m0\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[43mlast_step\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m+\u001B[39;49m\u001B[43m \u001B[49m\u001B[38;5;241;43m1\u001B[39;49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m5000\u001B[39;49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m\u001B[43m)\u001B[49m  \u001B[38;5;66;03m# 设置x轴标签\u001B[39;00m\n\u001B[0;32m     32\u001B[0m     axs[idx]\u001B[38;5;241m.\u001B[39mset_xlabel(\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mstep\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m     34\u001B[0m plt\u001B[38;5;241m.\u001B[39mshow()\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\matplotlib\\axes\\_base.py:74\u001B[0m, in \u001B[0;36m_axis_method_wrapper.__set_name__.<locals>.wrapper\u001B[1;34m(self, *args, **kwargs)\u001B[0m\n\u001B[0;32m     73\u001B[0m \u001B[38;5;28;01mdef\u001B[39;00m \u001B[38;5;21mwrapper\u001B[39m(\u001B[38;5;28mself\u001B[39m, \u001B[38;5;241m*\u001B[39margs, \u001B[38;5;241m*\u001B[39m\u001B[38;5;241m*\u001B[39mkwargs):\n\u001B[1;32m---> 74\u001B[0m     \u001B[38;5;28;01mreturn\u001B[39;00m \u001B[43mget_method\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;28;43mself\u001B[39;49m\u001B[43m)\u001B[49m\u001B[43m(\u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43margs\u001B[49m\u001B[43m,\u001B[49m\u001B[43m \u001B[49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[38;5;241;43m*\u001B[39;49m\u001B[43mkwargs\u001B[49m\u001B[43m)\u001B[49m\n",
      "File \u001B[1;32mC:\\Program Files\\Python312\\Lib\\site-packages\\matplotlib\\axis.py:2117\u001B[0m, in \u001B[0;36mAxis.set_ticklabels\u001B[1;34m(self, labels, minor, fontdict, **kwargs)\u001B[0m\n\u001B[0;32m   2113\u001B[0m \u001B[38;5;28;01melif\u001B[39;00m \u001B[38;5;28misinstance\u001B[39m(locator, mticker\u001B[38;5;241m.\u001B[39mFixedLocator):\n\u001B[0;32m   2114\u001B[0m     \u001B[38;5;66;03m# Passing [] as a list of labels is often used as a way to\u001B[39;00m\n\u001B[0;32m   2115\u001B[0m     \u001B[38;5;66;03m# remove all tick labels, so only error for > 0 labels\u001B[39;00m\n\u001B[0;32m   2116\u001B[0m     \u001B[38;5;28;01mif\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(locator\u001B[38;5;241m.\u001B[39mlocs) \u001B[38;5;241m!=\u001B[39m \u001B[38;5;28mlen\u001B[39m(labels) \u001B[38;5;129;01mand\u001B[39;00m \u001B[38;5;28mlen\u001B[39m(labels) \u001B[38;5;241m!=\u001B[39m \u001B[38;5;241m0\u001B[39m:\n\u001B[1;32m-> 2117\u001B[0m         \u001B[38;5;28;01mraise\u001B[39;00m \u001B[38;5;167;01mValueError\u001B[39;00m(\n\u001B[0;32m   2118\u001B[0m             \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124mThe number of FixedLocator locations\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m   2119\u001B[0m             \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m (\u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mlen\u001B[39m(locator\u001B[38;5;241m.\u001B[39mlocs)\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m), usually from a call to\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m   2120\u001B[0m             \u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m set_ticks, does not match\u001B[39m\u001B[38;5;124m\"\u001B[39m\n\u001B[0;32m   2121\u001B[0m             \u001B[38;5;124mf\u001B[39m\u001B[38;5;124m\"\u001B[39m\u001B[38;5;124m the number of labels (\u001B[39m\u001B[38;5;132;01m{\u001B[39;00m\u001B[38;5;28mlen\u001B[39m(labels)\u001B[38;5;132;01m}\u001B[39;00m\u001B[38;5;124m).\u001B[39m\u001B[38;5;124m\"\u001B[39m)\n\u001B[0;32m   2122\u001B[0m     tickd \u001B[38;5;241m=\u001B[39m {loc: lab \u001B[38;5;28;01mfor\u001B[39;00m loc, lab \u001B[38;5;129;01min\u001B[39;00m \u001B[38;5;28mzip\u001B[39m(locator\u001B[38;5;241m.\u001B[39mlocs, labels)}\n\u001B[0;32m   2123\u001B[0m     func \u001B[38;5;241m=\u001B[39m functools\u001B[38;5;241m.\u001B[39mpartial(\u001B[38;5;28mself\u001B[39m\u001B[38;5;241m.\u001B[39m_format_with_dict, tickd)\n",
      "\u001B[1;31mValueError\u001B[0m: The number of FixedLocator locations (15), usually from a call to set_ticks, does not match the number of labels (16)."
     ]
    },
    {
     "data": {
      "text/plain": [
       "<Figure size 1000x500 with 2 Axes>"
      ],
      "image/png": "iVBORw0KGgoAAAANSUhEUgAAA0UAAAGyCAYAAAArj289AAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAkd9JREFUeJzt3Qd4W+XVB/AjyXvHSZy9Q/YiCaEJK0BIIIyGMsIopBRoWR3QMltWaVlltmWUUlY/KKtAKYRASAgrIYEEyN57ecTx3rK+5/9K7/WVLMmSLNmW7//3PEosWbq+92rdc895z2tzuVwuISIiIiIisih7e68AERERERFRe2JQRERERERElsagiIiIiIiILI1BERERERERWRqDIiIiIiIisjQGRUREREREZGkMioiIiIiIyNIYFBERERERkaUxKCIiIiIiIktjUERERERERJbGoIiIiOLaZ599Jmeeeab07t1bbDabvPPOOy0+ZsmSJTJx4kRJTk6WoUOHygsvvNAm60pERB0TgyIiIoprlZWVMn78eHniiSdCuv+OHTvk9NNPlxNPPFG+++47+fWvfy1XXHGFfPjhhzFfVyIi6phsLpfL1d4rQUREFA3IFL399tsyZ86cgPe5+eab5f3335e1a9cat11wwQVSUlIiCxYsaKM1JSKijiRB4kBjY6Ps379fMjMz1RceERG1DZw3Ky8vV6VpdnvnKC5YtmyZzJgxw+u2WbNmqYxRILW1tepi/l4qLi6Wrl278nuJiKgTfDfFRVCEgKhfv37tvRpERJa1Z88e6du3r3QGBw8elB49enjdhutlZWVSXV0tqampzR5z3333yd13392Ga0lERG353RQXQREyRHrDs7Kywn58fX29fPTRRzJz5kxJTEyMyjrFyzJjtdx4WWaslmv1deX2x8/2txYCBZyU0p/DVnXrrbfKDTfcYFwvLS2V/v37R/y9REREHeu7KS6CIl2agC+eSIOitLQ09dhoHhTFwzJjtdx4WWaslmv1deX2x8/2R0tnKhHr2bOn5Ofne92G69jv/rJEgC51uPiK9HuJiIg61ndT5ygQJyIiCtHUqVNl0aJFXrctXLhQ3U5ERNbEoIiIiOJaRUWFaq2Ni265jZ93795tlL5deumlxv2vuuoq2b59u9x0002yceNGefLJJ+X111+X66+/vt22gYiI2heDIiIi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ld096NtWy/I5IiIiIiKyYFBU7QmKHHUsnyMiIiIiIisGRQ53+ZyjjuVzRERERERkwaCo1s6giIiIiIiILBwU1SS4g6KEepbPEUUT5vCBlubwoY6Nzx8REVmdJVpy13jK5xLqmSkiiqbExESjrXX37t0lKSnJCJQiPTivq6tTc/GYW1K3RrwsM5bLDcTlcqm/V1hYqP4enj8iIiIrskRQVOsJihLrK9p7VYg6FRxIYxJSTFzqb76fSA7Sq6urJTU1tVXBVTwuM5bLbUlaWpr079+/TQIxIiKijsgSQVF9Qqb6P7GBmSKiaEN2AQfUDQ0N4nQ6W7Ws+vp6+eyzz+T4449XWahoiJdlxnK5wTgcDklISGjTIIyIiKijsURQVJfozhQlsXyOKCZwQI2D+NYeyOMAHcFVSkpK1IKCeFlmLJdLREREwVmiVqLOkylKamD5HBERERERWTAoavBkipKdzBQREREREZEFg6L6xGz1f5KzCu2d2nt1iIiIiIioA7FEUOT0ZIps4hKp5VxFRERERERksaDIlpAsNS7PoOWa0vZeHSIiIiIi6kAsERQlOGxSJunuK8wUERERERGR5YIiu13KXanuK8wUERERERGRpTNFNcwUERERERGR1YIiu03KXGnuK8wUERERERGR5YIih13KhUERERERERFZNSgyZ4rYaIGIiIiIiKwWFCWqMUXMFBERERERkUWDIgfHFBERERERkdVbcjd1n2NQREREREREFiyf4zxFRERERERk2aDIK1PERgtERERERGS1oMjBTBEREREREVk5KEpEowVjTBEzRUREndETTzwhAwcOlJSUFDn66KNlxYoVQe//2GOPyfDhwyU1NVX69esn119/vdTU1LTZ+hIRUcdhiaAoweHTfc7lau9VIiKiKHrttdfkhhtukDvvvFNWrVol48ePl1mzZklBQYHf+7/yyityyy23qPtv2LBB/vnPf6pl3HbbbW2+7kRE1P6sERTZ7VKu5ylqrBdp4JlAIqLO5JFHHpErr7xSLrvsMhk1apQ8/fTTkpaWJs8995zf+y9dulSOOeYYueiii1R2aebMmXLhhRe2mF0iIqLOyRpBkcMmlZIiTr25HFdERNRp1NXVycqVK2XGjBnGbXa7XV1ftmyZ38dMmzZNPUYHQdu3b5f58+fL7Nmz/d6/trZWysrKvC5ERNR5JIgFJNhtImKTCkmTbKlwB0WZPdt7tYiIKAqKiorE6XRKjx49vG7H9Y0bN/p9DDJEeNyxxx4rLpdLGhoa5KqrrgpYPnfffffJ3XffHZP1JyKi9meNTJHdvZlGCR2bLRARWdqSJUvk3nvvlSeffFKNQXrrrbfk/fffl3vuucfv/W+99VYpLS01Lnv27GnzdSYiog4SFOFM2VFHHSWZmZmSl5cnc+bMkU2bNrX4uDfeeENGjBihOgKNHTtWlSi0dfkcIFOksHyOiKjT6NatmzgcDsnPz/e6Hdd79vRfFXD77bfLJZdcIldccYX6Xjr77LNVkITvucbGxmb3T05OlqysLK8LERFZNCj69NNP5dprr5WvvvpKFi5cKPX19WpwamVlZcDHYDArBq9efvnl8u2336pACpe1a9dKW0n0BEVlLj2BK4MiIqLOIikpSSZNmiSLFi0ybkNgg+tTp071+5iqqio17sgMgRWgnI6IiKwlrDFFCxYs8Lr+wgsvqIwRBqsef/zxfh/z+OOPy6mnnio33nijuo7SBARUf/vb31R3oLbgUGOKREqZKSIi6pTQjnvevHkyefJkmTJlipqDCCfs0I0OLr30UunTp4/KBMGZZ56pOtYdeeSRak6jrVu3quwRbtfBERERWUerGi2grhpyc3MD3gedf/BlZYa5I955552Aj0GXH1w03eUHmSlcwtbodC+nMVXEIeKsPCyNkSzHRK9HROvThsuM1XLjZZmxWq7V15XbHz/b31odaV2CmTt3rhQWFsodd9whBw8elAkTJqgTebr5wu7du70yQ7///e/FZrOp//ft2yfdu3dXAdGf/vSndtwKIiJqLzZXhHUCKE0466yzpKSkRL744ougZQ0vvviiKqHTMLAVXXx867+1u+66y2+XH0y2h3knwlVWJ3L7ygS5I+El+WnCAtnc40zZ0Pu8sJdDRGQ1KDNDpzacBOM4GvE6WZednc39QkTUST6DI84UYWwRxgUFC4gihS4/5uwSNrxfv35q/FIkG15YViW3r/zC6D43pE83GXSa/7kowjl7ijLAU045RRITE1u1rFguM1bLjZdlxmq5Vl9Xbn/8bH9rcT4eIiKygoiCouuuu07ee+89+eyzz6Rv375B74vOP+F0BNJdfnDxhYOESA4UUpKS1P9lLndQ5KivEEeUDjgiXae2Xmaslhsvy4zVcq2+rtz++Nn+SHWU9SAiIuow3edQaYeA6O2335bFixfLoEGDWnwMOv+YOwIBzoQG6ggU0+5zbLRAREREREStyRShZA7jev773/+quYowmBVQ05eamuq3w8+vfvUrOeGEE+Thhx+W008/XV599VX55ptv5JlnnpG2kuDpPqczRQyKiIiIiIgookzRU089pQY0TZ8+XXr16mVcXnvtNeM+6PBz4MAB4/q0adNUIIUgaPz48fLmm2+qznNjxoyRtqJbcpeJZ56iGtbIExERERFRBJmiUBrVLVmypNlt5513nrq0F7RdtdtcUu5yZ7OYKSIiIiIioogyRfEMw4qMTFEtM0VERERERGTFoEiPKaqrEHE2tPcqERERERFRB2CpoKhCPOVzwGwRERERERFZKShCr4UGSZDGBHagIyIiIiIiCwZFnqmKxJmU6f6BQREREREREVkxKGpIynL/wPI5IiIiIiKyZFCU6AmKmCkiIiIiIiIrBUWe+VulITHD/QODIiIiIiIismKmqM7IFLF8joiIiIiILJgpqmemiIiIiIiILJ0pSvB0n2OjBSIiIiIismJQVOtgS24iIiIiIrJgUGS3udT/dQnp7hsYFBERERERkRUzRTXMFBERERERkSWDIs+W1jiYKSIiIiIiIgtniqrtbLRAREREREQWbsldbU9z/8BMERERERERWTJT5NDzFJWJuNzNF4iIiIiIyLosFxRV2T1BkcspUlfZrutERERERETtz3JBUY0rScSe4LnCEjoiIiIiIquz3Jii+kYRScl2X2GzBSIiIiIiy7NcpsjZ6BJJznJfYaaIiIiIiMjyLJgpamzKFKHZAhERERERWZrlMkUNTpcpKGKmiIiIiIjI6iwYFCFTpMvnStp1nYiIiIiIqP1ZJiiy29xzEjVgTBEbLRARERERkaXL55JZPkdERERERBYNithogYiIiIiILN19jo0WiIiIiIjIkkGR1zxFRqMFBkVERERERFZnnaDIs6X1qvscGy0QEREREZHVgiJdPodMUTIzRUREREREZNExRV6ZIjZaICIiIiKyPIuOKWKjBSIiIiIismhQ5O4+5ymfa6gWaahr1/UiIiIiIqL2Zc15ivSYImCzBSIiIiIiS7PmPEV2h0hSpvsGltAREREREVmaNbvPAccVERERERGRJYMidJ8DBkVERERERGSloMhuc/lkijzjijim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p/scGy0QEREREZEVg6Kg8xQxU0REREREZEnWbMnt1WjBExTVlYs0OttpzYiIiIiIqL1YKijy25JbjykCNlsgIiIiIrIcawVFxuStpkxRQpJIQqr7Z5bQERERERFZjjW7z5nHFAGbLRARERERWZY1Gy2Yy+eAzRaIiIiIiCzLmi25zY0WIMUzrohjioiIiIiILMeS5XNejRaAmSIiIiIiIsuyZKMFp++YIt2BjkEREREREZHlWCoochgtuX3L59hogYiIiIjIqiw6eatv+RwzRUREREREVmWpoCjB6D4XIFNUy6CIiIiIiMhqLNp9jo0WiIiIiIjIikFRoHmKkhkUERERERFZlTWDombzFLHRAhERERGRVVmyfK75PEVstEBEREREZFUWC4oCzFNkNFpgpoiIiIiIyGosWT4XeJ6iUhGXT8BERERERESdmiWDombd55I95XONDSL1Ve2wZkRERERE1F4sOaYI5XMuc0YoKV3E5nD/zGYLRERERESWYqmgKNGTKWqWLbLZ2GyBiIiIiMiiLBUUOcxBUbMOdGy2QERERERkRZYsn4P6gHMVMVNERERERGQl1i2fcwZotsCgiIiIiIjIUiwVFNntNtFxUQMzRUREREREZLWgCBLs9uBjihgUERERERFZivWCIodnriI2WiAiIiIiIksGRZ76OTZaICIiIiIiSwZFiaYJXL2w0QIRERERkSVZtnyu3hkoU8TyOSIiIiIiK7FeUBSw0QIzRUREREREVmTdRgumMUWbDpbLgq3V7itstEBEREREZCkJYtFGC+ZM0V3vrpOyHQVyajIzRUREREREVpNg1UYLDaZGC7uLq8Quae4rHFNERERERGQpliufc9i9Gy24XC4pLK+VMle6+w71lSLO+vZcRSIiIiIiakOWC4oSdKbIUz5XUlUvdc5GqZDUpjsxW0REREREZBmWC4oS9ZgiT/lcQXmt+t8pDqmze0roajmuiIiIiIjIKuxW7z5XUF5j/K7G4SmhY7MFIiIiIiLLsFt9nqL8MnemCKrsGe4fWD5HRERERGQZls0U6UYL5kxRpY2ZIiIiIiIiq7FspsipxxSZMkXlRltuBkVERERERFZhuaAoUWeKjEYLNc2DolqWzxERERERWYVl5ylq0OVzpkxRqYuZIiIiIiIiq7FcUJToM09RvilTdNjpmauIjRaIiIiIiCzD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240OZZ2p0UBLdAe6zjieiIiIiOIfgyIiH6eO6Smb8svVgfyZ43qrTE9rDfJMLlte06D+n3Nk/DVYaI3RvbNUG+46Z6McN7R7e68OERERkRcGRUR+xhTdeeboqC4TmSIN8/pMG2KtbElaUoI8fclEqapzxt28TERERNT5MSgiagN6riI4a3xvS46pOWlE/I6hIiIios6N3eeI2oBuSQ1nx3HXOSIiIqLOiJkiojbQIytFNWxwuVxqfA0RERERdRwMiojayC2njWjvVSAiIiIiP1g+R0RERERElsagiIiIiIiILC2ioOiJJ56QgQMHSkpKihx99NGyYsWKoPd/4403ZMSIEer+Y8eOlfnz50e6vkRERERERO0bFL322mtyww03yJ133imrVq2S8ePHy6xZs6SgoMDv/ZcuXSoXXnihXH755fLtt9/KnDlz1GXt2rXRWH8iIiIiIqK2DYoeeeQRufLKK+Wyyy6TUaNGydNPPy1paWny3HPP+b3/448/LqeeeqrceOONMnLkSLnnnntk4sSJ8re//a11a05ERERERNTW3efq6upk5cqVcuuttxq32e12mTFjhixbtszvY3A7MktmyCy98847Af9ObW2tumhlZWXq//r6enUJl35MJI+N92XGarnxssxYLdfq68rtj5/tb62OtC5ERESxYnNh4pQQ7d+/X/r06aNK4qZOnWrcftNNN8mnn34qy5cvb/aYpKQkefHFF1UJnfbkk0/K3XffLfn5+X7/zl133aV+7+uVV15RWSkiImobVVVVctFFF0lpaalkZXGOLfPJuuzsbO4XIqJO8hncIecpQibKnF3Chvfr109mzpwZ0YbjTOfChQvllFNOkcTExKisY7wsM1bLjZdlxmq5Vl9Xbn/8bH9r6Uw9ERFRZxZWUNStWzdxOBzNMjy43rNnT7+Pwe3h3B+Sk5PVxRcOElpzoNDax8fzMmO13HhZZqyWa/V15fbHz/ZHqqOsBxERUYdptIBSuEmTJsmiRYuM2xobG9V1czmdGW433x9wJjTQ/YmIiIiIiNpS2OVzKGubN2+eTJ48WaZMmSKPPfaYVFZWqm50cOmll6pxR/fdd5+6/qtf/UpOOOEEefjhh+X000+XV199Vb755ht55plnor81REREREREsW7JPXfuXHnooYfkjjvukAkTJsh3330nCxYskB49eqjf7969Ww4cOGDcf9q0aapBAoIgzGn05ptvqs5zY8aMCfdPExERBcSJxYmIqE0bLVx33XXq4s+SJUua3XbeeeepCxERUSzoicUxdx4CIlQxYPqHTZs2SV5eXsCJxVHVcMYZZ6iTd5hYHJOS86QdEZH1hJ0pIiIi6mg4sTgREXW6lty+9FRKkbaGRZtbzLWBx0ezJW88LDNWy42XZcZquVZfV25//Gx/a+nP3TCmtGtzbTGxuO+k4pgbA9iynIioc3w3xUVQVF5erv7HXEVERNQ+n8OYKK8jKioqEqfTaYxt1XB948aNfh9z8OBBv/fH7f6gzM7fpOL8XiIiaj+HDh2K2ndTXARFvXv3lj179khmZqbYbLawH68nf8UyojXrbbwsM1bLjZdlxmq5Vl9Xbn/8bH9r4SwcAiJ8DluZ76TiJSUlMmDAANVcqKMGi+2lI76OOwLul8C4b/zjfgkM2fr+/ftLbm6uREtcBEUog+jbt2+rl4MXVLRfVPGyzFgtN16WGavlWn1duf3xs/2t0dEP+ttiYvFAk4pj33Sk56oj6Wiv446C+yUw7hv/uF+CxwjRwkYLREQU1zixOBERWSJTREREFAwnFiciotawRFCEkoc777zTb+lDZ19mrJYbL8uM1XKtvq7c/vjZfqvAxOKFhYVqYnE0S8Dk4r4Ti5vLLPTE4r///e/ltttukyOOOCKsicX5XAXGfeMf90tg3Df+cb+07b6xuTpyn1UiIiIiIqIY45giIiIiIiKyNAZFRERERERkaQyKiIiIiIjI0hgUERERERGRpXX6oOiJJ56QgQMHSkpKihx99NGyYsUK43d33XWX2Gw2r8uIESOM39fU1Mi1114rXbt2lYyMDDnnnHOaTfb3xhtvqO5G+vFz5syRhoYG4/foY4FWsImJier3aWlpcv/993sto7i4WC6++GI1MVdOTo5qD3vaaaepGeTxGHREWr16tRx33HFqO9LT05ut96mnnqrWBeuP+4wcOVJOPPFEY5loU4t5PDIzMyUvL0+t57vvvmssEzMm/+lPf2q2vf/4xz+MZY4dO1ZeeOEFtX7YDizn+OOPV8s2L/ell16SiRMnqo4gQ4cOVY/3Xd9jjz3W63nBuujlYlndu3c3JivDvCEffPCBLFmyRC0Xc5JgwkSso+/zop9v/G1sN5aP9TrllFPU+puX+cADDxjrmZqa2mwdr7rqKq/XD7pZHXPMMca233jjjV7PNZ5XPA6/09uO/ee7T9ESONC2h7rccPepv9cMltGafYr1RKcv3+Xi95Gup17url275Mc//rHab3huBg8erJall/v888+rv92rVy/1+xkzZqguNOblYs4Z8/vqggsuUBe9TLwe/v3vf7fqfYXXqu9jcMF7F/fHpNPocub7GWJe5vDhw+Woo44K+PyDfp709uN9GM5nHUUm3H1qfl7x+po/f750VuHsG3wO4n3WpUsXdcH7tbO+PiN9H6IlvD6G6KzC3TclJSXq+xOf8/jsGzZsWKd8T4W7XzDdAL438D2GY7frr79eHa92Jp999pmceeaZXsfBLQnle7JFrk7s1VdfdSUlJbmee+4517p161xXXnmlKycnx5Wfn69+f+edd7pGjx7tOnDggHEpLCw0Hn/VVVe5+vXr51q0aJHrm2++cf3gBz9wTZs2zfh9Q0ODa8CAAa6BAwe6Hn74YXTxc2VmZrpuvfVW4z433XSTuv2ss85yvf32266xY8eq6++++65xn1NPPdU1fvx411dffeX6/PPPXb169VLr9dZbb6n7vvzyy64ePXq4Lr74YtfatWtdxx9/vMtut7sefPBBY70/+OADl8PhULetX7/eNWTIEOOxWGZaWprr6KOPVo//7rvvXDNnzlTLmDt3rrrt3//+t3p8ly5djO3FOmAZepm33Xabuo798O2337rmz5/vSkxMdJ1++unGcqdPn+6y2WyuX/ziF+oxf/3rX9VjTjvtNGNdn376afU4/bxcfvnlal2wXVgunpesrCzXz3/+c9emTZvU38X9U1JSXDfccINaZ6wnHoPl6+dFP9/PPvus64gjjnD16dPHlZGR4XrllVfU83Leeee5Nm/erJZ5zTXXqPW69NJL1XoOHTpUrTfuq9cT66dfP6tXr1Z/MyEhQe0fbHu3bt2M53rFihWuvn37qmUceeSRxrbjevfu3Y19ivXCbYG2PdTlhrtPL7roIvX84nnRj8H64HUR6T7Feh577LHGe2j58uWu1NRU19VXXx3xemL7c3NzXdnZ2a6f/OQnapmffvqpKzk52fXTn/7Ua7+mp6e73nnnHdf333/vmjRpkvpbf//73433Op4rrBveV1gurg8aNEgtc/v27er91bVr11a9r7C8OXPmGPfH+uD3M2bMUMvE/1jX66+/3vgM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"
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "execution_count": 11
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:37:53.242389Z",
     "start_time": "2025-01-17T04:37:53.242389Z"
    }
   },
   "cell_type": "code",
   "source": "record_dict[\"train\"][-5:]",
   "id": "775b113bb78383fa",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": "record_dict[\"val\"][-10:]",
   "id": "dbb21c010aaef95e",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {},
   "cell_type": "markdown",
   "source": "## 评估以及上线",
   "id": "1fa2f200745f7b63"
  },
  {
   "metadata": {},
   "cell_type": "code",
   "source": [
    "model = NeuralNetwork()  #上线时加载模型\n",
    "model = model.to(device)  # 将模型移到GPU上"
   ],
   "id": "b106406c03208c15",
   "outputs": [],
   "execution_count": null
  },
  {
   "metadata": {
    "ExecuteTime": {
     "end_time": "2025-01-17T04:43:37.385769Z",
     "start_time": "2025-01-17T04:43:35.968799Z"
    }
   },
   "cell_type": "code",
   "source": [
    "# 加载最好的模型\n",
    "# torch.load：加载保存的模型权重或整个模型。\n",
    "# \"checkpoints/best.ckpt\"：模型权重文件路径。\n",
    "# weights_only=True：仅加载模型的权重，而不是整个模型（包括结构和参数）。这是 PyTorch 2.1 引入的新特性，用于增强安全性。\n",
    "# map_location=device：将模型加载到当前设备（GPU或CPU）。\n",
    "model.load_state_dict(torch.load(\"checkpoints/05_best.ckpt\", weights_only=True, map_location=device))  # 加载最好的模型\n",
    "\n",
    "model.eval()  # 评估模式\n",
    "loss, acc = evaluate(model, test_loader, loss_fct)\n",
    "print(f\"Test loss: {loss:.4f}, Test acc: {acc:.4f}\")"
   ],
   "id": "26cdacda63d5388e",
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Test loss: 0.3452, Test acc: 0.8826\n"
     ]
    }
   ],
   "execution_count": 12
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 2
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython2",
   "version": "2.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
